#include "SurfaceDirectDraw.h"

#include "RenderMethodDirectDraw.h"

namespace YoghurtGum
{

	SurfaceDirectDraw::SurfaceDirectDraw(int a_Width, int a_Height, uint32 a_Flags) : Surface(a_Width, a_Height)
	{
		m_Flags = a_Flags;
		Init(a_Width, a_Height);
	}

	SurfaceDirectDraw::~SurfaceDirectDraw()
	{

	}

	void SurfaceDirectDraw::Fill(Color a_Color)
	{
		m_ClearSettings.dwFillColor = RenderMethodDirectDraw::GetSingleton()->GetFillColor(a_Color);

		if (ErrorDirectDraw::Failed(m_Surface->Blt(
					NULL, 
					NULL,
					NULL, 
					DDBLT_COLORFILL, 
					&m_ClearSettings
				)
			)
		)
		{
			ERROR_EXPLAIN("Couldn't clear the surface.");
		}
	}

	bool SurfaceDirectDraw::DumpTo(Surface* a_Target)
	{
		if (ErrorDirectDraw::Failed(((SurfaceDirectDraw*)a_Target)->m_Surface->Blt(
					&m_Dimensions, 
					m_Surface,
					NULL, 
					NULL, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Couldn't dump this surface on another.");
			return false;
		}

		return true;
	}

	bool SurfaceDirectDraw::CopyTo(Surface* a_Target, int a_X, int a_Y)
	{
		RECT plot;
	
		int32 x = a_X;
		int32 y = a_Y;
		int32 width = x + m_Width;
		int32 height = y + m_Height;

		//if (x < 0) { x = 0; width -= x; }

		SetRect(
			&plot,
			x, y,
			width, height
		);

		if (ErrorDirectDraw::Failed(((SurfaceDirectDraw*)a_Target)->m_Surface->Blt(
					&plot, 
					m_Surface,
					NULL, 
					NULL, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Couldn't copy this surface to another. ((%i %i)(%i %i))", a_X, a_Y, m_Width, m_Height);
			return false;
		}

		return true;
	}

	bool SurfaceDirectDraw::Draw(int a_X, int a_Y)
	{
		RECT plot;
		CopyRect(&plot, &m_Dimensions);
		OffsetRect(&plot, a_X, a_Y);

		RECT dim;
		CopyRect(&dim, &m_Dimensions);

		LPDIRECTDRAWSURFACE target = RenderMethodDirectDraw::GetSingleton()->GetBackBuffer();

		if (a_X < 0 || a_Y < 0)
		{
			LPDIRECTDRAWCLIPPER clipper = NULL;
			target->GetClipper(&clipper);
			if (!clipper)
			{
				ERROR_EXPLAIN("Trying to draw outside the target surface but no clipper is attached.");
				return false;
			}
		}

		if (a_X < 0) 
		{
			// shrink by difference
			// offset by difference
			dim.left -= plot.left;
			dim.right += plot.left;

			// subtract different from right
			plot.right += plot.left;
			plot.left = 0;

			// if the surface is still greater than the backbuffer
			int32 back_width = RenderMethodDirectDraw::GetSingleton()->GetBackbufferWidth();
			if (dim.right > back_width)
			{
				int32 diff = dim.right - back_width;
				dim.right -= diff;
				plot.right -= diff;
			}
		}

		if (a_Y < 0)
		{
			dim.top -= plot.left;
			dim.bottom += plot.left;

			plot.bottom += plot.top;
			plot.top = 0;
		}

		

		if (plot.right <= 0)
		{
			//YG_LOG_DEBUG("Sprite is clipped completely, not drawing.");
			return false;
		}

		if (ErrorDirectDraw::Failed(target->Blt(
					&plot, 
					m_Surface,
					&dim, 
					NULL, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Couldn't copy to screen. (%i, %i, %i, %i)", plot.left, plot.top, plot.right, plot.bottom);
			Log::GetSingleton().Print("Dimensions: (%i, %i, %i, %i)", dim.left, dim.top, dim.right, dim.bottom);
			return false;
		}

		return true;
	}

	void SurfaceDirectDraw::Line(float a_FromX, float a_FromY, float a_ToX, float a_ToY, Color a_Color)
	{
		//based on Jacco Bikker's implementation of the line drawing algorithm Dr. Bresenham developed

		if ((int)(a_FromX - a_ToX) != 0 || (int)(a_FromY - a_ToY) != 0)
		{ 
			YGC color = a_Color.GetColor();

			// are the steps determined by the x or the y axis?
			float xlength = Math::Abs(a_ToX - a_FromX);
			float ylength = Math::Abs(a_ToY - a_FromY);
			float length = (xlength > ylength) ? xlength : ylength;
			int steps = Math::Round(length);

			float stepX = 0;
			float stepY = 0;
			if (length > 0)
			{
				stepX = ((a_ToX - a_FromX) / steps);
				stepY = ((a_ToY - a_FromY) / steps);
			}

			// start at (x1, y1) go to (x2, y2)
			float drawX = a_FromX;
			float drawY = a_FromY;

			// does the line fall within the surface boundaries?
			if (
				(a_FromX >= 0 && a_FromX < m_Width  && a_ToX >= 0 && a_ToX < m_Width ) &&
				(a_FromY >= 0 && a_FromY < m_Height && a_ToY >= 0 && a_ToY < m_Height)
			)
			{
				// no need for per pixel checking!
				for (int i = 0; i <= steps; i++)
				{
					int plot = (Math::Round(drawY) * m_Pitch) + (int)(Math::Round(drawX));

					m_Buffer[plot] = color;

					drawX += stepX;
					drawY += stepY;
				}
			}
			else
			{
				for (int i = 0; i <= steps; i++)
				{
					// check if a pixel falls within our view
					// slower, but better line drawing around the edges
					if (drawX > 0 && drawX < m_Width &&
						drawY > 0 && drawY < m_Height)
					{
						int plot = (Math::Round(drawY) * m_Pitch) + (int)(Math::Round(drawX));

						m_Buffer[plot] = color;
					}

					drawX += stepX;
					drawY += stepY;
				}
			}
		}
	}

	void SurfaceDirectDraw::Init(int a_Width, int a_Height)
	{
		m_Width  = a_Width;
		m_Height = a_Height;

		YG_ZEROMEMORY(&m_Settings, sizeof(m_Settings));
		m_Settings.dwSize	= sizeof(m_Settings);

		m_Settings.dwFlags	= DDSD_WIDTH | DDSD_HEIGHT;
		m_Settings.dwWidth	= a_Width;
		m_Settings.dwHeight	= a_Height;

		SetRect(
			&m_Dimensions, 
			0, 0, 
			a_Width, a_Height
		);

		if (ErrorDirectDraw::Failed(RenderMethodDirectDraw::GetSingleton()->GetObject()->CreateSurface(
					&m_Settings, 
					&m_Surface, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to create surface.");
			return;
		}

		if (m_Flags & YG_SURFACE_CLIPPER)
		{
			if (ErrorDirectDraw::Failed(RenderMethodDirectDraw::GetSingleton()->GetObject()->CreateClipper(
						0,
						&m_Clipper,
						NULL
					)
				)
			)
			{
				ERROR_EXPLAIN("Failed to create a clipper.");
				return;
			}

			if (ErrorDirectDraw::Failed(m_Clipper->SetHWnd(
						0, 
						RenderMethodDirectDraw::GetSingleton()->GetWindowHandle()
					)
				)
			)
			{
				ERROR_EXPLAIN("Failed to attach clipping device to window.");
				return;
			}

			if (ErrorDirectDraw::Failed(m_Surface->SetClipper(
						m_Clipper
					)
				)
			)
			{
				ERROR_EXPLAIN("Failed to attach clipping device to backbuffer.");
				return;
			}
		}

		if (ErrorDirectDraw::Failed(m_Surface->GetSurfaceDesc(
					&m_Settings
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to get properties.");
			return;
		}

		DDPIXELFORMAT format;
		RenderMethodDirectDraw::GetSingleton()->GetBackBuffer()->GetPixelFormat(&format);
		m_BPP = format.dwRGBBitCount >> 3;

		m_Pitch = m_Settings.lPitch / m_BPP;
		//printf("Pitch: %i %i %i\n", m_Settings.lPitch, m_Settings.lXPitch, m_Settings.dwSurfaceSize);
	
		ZeroMemory(&m_ClearSettings, sizeof(m_ClearSettings));
		m_ClearSettings.dwSize = sizeof(m_ClearSettings);

		SetBuffer();
	}

	void SurfaceDirectDraw::SetBuffer()
	{
		if (ErrorDirectDraw::Failed(m_Surface->Lock(
					NULL,
					&m_Settings, 
					0, 
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to lock surface.");
			return;
		}
		
		/*DDPIXELFORMAT format = m_Settings.ddpfPixelFormat;
		printf("BPP: %i\n", format.dwRGBBitCount);*/

		// TODO: breaks when using different bpp
		m_Buffer = (YGC*)m_Settings.lpSurface;
	
		if (ErrorDirectDraw::Failed(m_Surface->Unlock(
					NULL
				)
			)
		)
		{
			ERROR_EXPLAIN("Failed to unlock surface.");
			return;
		}

		if (ErrorDirectDraw::Failed(m_Surface->Restore()))
		{
			ERROR_EXPLAIN("Could not restore surface.");
			return;
		}
	}

	YGC* SurfaceDirectDraw::GetBuffer()
	{
		return m_Buffer;
	}

	void SurfaceDirectDraw::SetBuffer(YGC* a_Buffer)
	{
		memcpy(m_Buffer, a_Buffer, m_Pitch * m_Height * m_BPP);
	}

	void SurfaceDirectDraw::Lock()
	{
		m_Locked = true;
	}

	void SurfaceDirectDraw::Unlock()
	{
		m_Locked = false;
	}

	void SurfaceDirectDraw::Resize(int a_Width, int a_Height)
	{
		YG_STUB;
	}

	void SurfaceDirectDraw::Plot(int a_PosX, int a_PosY, Color a_Color)
	{
		YG_STUB;
	}

	void SurfaceDirectDraw::Square(int a_StartX, int a_StartY, int a_EndX, int a_EndY, Color a_Color)
	{
		YG_STUB;
	}

}; // namespace YoghurtGum